/*
 	Copyright 2007 Amazon Technologies, Inc.  Licensed under the Apache License, Version 2.0 (the "License"); 
	you may not use this file except in compliance with the License. You may obtain a copy of the License at:

	http://aws.amazon.com/apache2.0

	This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
	See the License for the specific language governing permissions and limitations under the License.  
 */

package com.inspectortime.webapp.util.fps;

import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Category;

/**
 * Helper class for signing URLs for integrating with Amazon FPS co-branded UI.
 * 
 * @author FPS SDK Team
 *
 */
public class SigHelper {
	
	/**
	 * The log4j Category object.
	 */
	private static Category log = Category.getRoot();
	
	/**
	 * The name of the algorithm used for signing the unsigned URL.
	 */
	private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";

	
	/**
	 * Gets the signed URL, given an unsigned URL and secret key.
	 * 
	 * @param url	the unsigned URL
	 * @param key	the secret key which is to be used to sign the url
	 * @return		the signed URL
	 * @throws 		Exception
	 */
	public static String getSignedUrl(String url, String key) throws Exception {
		return url + "&awsSignature=" + URLEncoder.encode(getSignature(url, key),"UTF-8");
	}

	/**
	 * Get the signature, given an unsigned URL and secret key.
	 * 
	 * @param url	the unsigned URL
	 * @param key	the secret key which is to be used to sign the url
	 * @return		the value of the signature
	 * @throws Exception
	 */
	public static String getSignature(String url, String key) throws Exception {
		URL u = new URL(url);
		return computeSignature(key, u.getPath(),
				makeParamMap(u.getQuery()));
	}

	/**
	 * Creates the string to be signed by appending sorted parameter 
	 * name and value pairs, being passed as a part of the URL, to the 
	 * path obtained from the URL. Then calls calculateSig method and 
	 * passes this string. 
	 * 
	 * @param callerSecretKey	the key to be used for signing the unsigned URL
	 * @param reqURI			the path of the FPS co-branded UI URL
	 * @param paramMap			the map containing the query parameters 
	 * 							name and value pairs passed in the URL	
	 * @return					the signature value obtained from the 
	 * 							calculateSig method call
	 * @throws Exception
	 */
	private static String computeSignature(String callerSecretKey,
			String reqURI, Map paramMap) throws Exception {
		
		StringBuffer strToSign = new StringBuffer(reqURI);

		/* Raise exception if caller key unknown */
		if (callerSecretKey == null || callerSecretKey.equals(""))
			throw new RuntimeException("Invalid callid for computeSignature");
		boolean first = true;

		TreeMap sortedMap = new TreeMap(paramMap);
		for (Iterator i = sortedMap.entrySet().iterator(); i.hasNext();) {
			Map.Entry e = (Map.Entry) i.next();
			String key = (String) e.getKey();
			String[] value = (String[]) e.getValue();
			for (int j = 0; j < value.length; j++) {
				log.info("SigHelper.java: From parameter '" + key	+ "' adding " + " value '" + value[j] + "'");
				if (first)
					strToSign.append('?');
				else
					strToSign.append('&');
				strToSign.append(key);
				strToSign.append('=');
				strToSign.append(URLEncoder.encode(value[j],"UTF-8"));
				first = false;
			}
		}

		log.info("SigHelper.java: Attempting to sign string '"	+ strToSign.toString());

		return calculateSig(callerSecretKey, strToSign.toString());
	}

	/**
	 * @param secretKey				the key to be used for signing the unsigned URL
	 * @param parametersToHash		the string to be signed
	 * @return						the signature obtained by signing the <code>parametersToHash</code>
	 * 								with the <code>secretKey</code> using the <code>HMAC_SHA1_ALGORITHM</code>
	 * 								algorithm
	 */
	private static String calculateSig(String secretKey, String parametersToHash) {

		String result;

		try {
			SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes(),
					HMAC_SHA1_ALGORITHM);

			Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
			mac.init(signingKey);

			byte[] rawHmac = mac.doFinal(parametersToHash.getBytes());

			String encodedHMac = new String (Base64.encodeBase64Chunked (rawHmac));	
			// The following API behaves differently on windows and Linux
			if (System.getProperty("os.name").startsWith("Windows"))
			{
				result = encodedHMac.substring(0, encodedHMac.length() - 2);
			}
			else 
			{			
				result = encodedHMac.substring(0, encodedHMac.length() - 2);
			}
		} catch (Exception e) {
			throw new RuntimeException("Error Calculating Hmac", e);
		}
		return result;
	}

	/**
	 * Creates a a hash map containing the arguments passed in the unsigned URL 
	 * as key value pairs.
	 * 
	 * @param queryString	the query string part of the unsigned URL
	 * @return				a hash map containing the arguments passed in the 
	 * 						unsigned URL as key value pairs
	 * @throws UnsupportedEncodingException
	 */
	private static Map makeParamMap(String queryString) throws UnsupportedEncodingException {
		HashMap hmp = new HashMap();
		String[] param = queryString.split("&");
		for (int i = 0; i < param.length; i++) {
			int index = param[i].indexOf("=");
			String key = param[i].substring(0, index);
			String value = param[i].substring(index + 1);
			log.info("SigHelper.java: Adding " + key + " = " + value);
			if (hmp.get(key) == null)
				hmp.put(key, new String[] { URLDecoder.decode(value,"UTF-8") });
			else {
				// Very Rare Case
				String[] arr = (String[]) hmp.get(key);
				String[] arrNew = new String[arr.length + 1];
				arrNew[arr.length] = URLDecoder.decode(value,"UTF-8");
				for (int j = 0; j < arr.length; j++) {
					arrNew[j] = arr[j];
				}
			}
		}
		return hmp;
	}
}
